﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;

using HorrorEncodeLib;

namespace HorrorEncodeLib {
    public class Decode {
        /// <summary>
        /// Recibe una cadena de Bits y devuelve el Mensaje Decodificado que representa.
        /// </summary>
        /// <param name="obj">Cadena de Bits que contiene el Mensaje Codificado</param>
        /// <param name="encodeMap">Mapa de Codificación con el que decodificará el Mensaje (generalmente nunca se especifica manualmente)</param>
        /// <returns>Mensaje Decodificado</returns>
        public static string FromBoolList(List<bool> obj, EncodeMappings encodeMap = EncodeMappings.SmartMapping) {
            // > Hacer una validación rápida de tamaño
            if ( obj.Count < 6 )
                throw new Exception("El BitArray tiene muy pocos elementos para contener un mensaje codificado");

            // > Obtener el EncodeMap (Mapa de Codificación) utilizado si no se especificó
            if ( encodeMap == EncodeMappings.SmartMapping ) {
                // > Iteramos cabecera por cabecera para encontrar el Encode Map (Mapa de Codificación)
                foreach ( KeyValuePair<EncodeMappings, BitArray> header in EncodeMaps.Headers ) {
                    // > Determinar si éste Encode Map es el que se encuentra en la cabecera
                    bool equals = true;
                    for ( int i = 0; i < EncodeMaps.HeadersSize; i++ )
                        equals = (header.Value[i] != obj[i]) ? false : equals;

                    // > Almacenar el Encode Map (Mapa de Codificación) que acabamos de encontrar
                    if ( equals ) {
                        encodeMap = header.Key;
                        break;
                    }
                }

                // > Si no se encontró ningún mapa en el proceso anterior lanzar Excpeción
                if ( encodeMap == EncodeMappings.SmartMapping )
                    throw new Exception("No se detectó un Encode Map (Mapa de Codificación)");

                // > Eliminamos la cabecera del List<bool>
                for ( int i = 0; i < EncodeMaps.HeadersSize; i++)
                    obj.RemoveAt(0);
            }

            // > Inicializar variables que nos permitirán decodificar el Mensaje
            StringBuilder currentString = new StringBuilder();
            List<bool> currentBits      = new List<bool>();

            // > Iterar cada Bit del Mensaje Codificado
            foreach ( bool bit in obj ) {
                // > Agregar éste bit a la cadena de bits actual
                currentBits.Add(bit);

                // > Inicializar variables que permiten determinar qué carácter está codificado
                ushort matches = 0;
                KeyValuePair<char, BitArray> lastMatch = new KeyValuePair<char, BitArray>();

                // [!] - Los siguientes procesos pueden optimizarse, pero aún no lo he hecho. La siguiente
                //       versión tendrá éste procedimiento optimizado, no se asusten por favor.

                // > Iteramos entre todos los Carácteres del Encode Map (Mapa de Codificación) y encontramos
                //   el que coincide con el que tenemos en nuestra cadena bits hasta ahora
                foreach ( KeyValuePair<char, BitArray> mask in EncodeMaps.Maps[encodeMap] ) {
                    // > Determinamos si ésta Máscara coincide con el Carácter del Encode Map (Mapa de Codificación)
                    bool match = true;
                    for ( ushort i = 0; (i < mask.Value.Length) && (i < currentBits.Count); i++ )
                        match = (currentBits[i] != mask.Value[i]) ? false : match;

                    // > Almacenamos la Última Coincidencia y aumentar el número de Coincidencias
                    if ( match ) {
                        matches += 1;
                        lastMatch = mask;
                    }
                }

                // > Agregamos éste carácter al Mensaje Decodificado y vaciamos ésta Cadena de Bits
                if ( matches == 1 ) {
                    currentString.Append(lastMatch.Key.ToString());
                    currentBits.Clear();
                }
            }

            // > Devolvemos el Mensaje Decodificado
            return currentString.ToString();
        }

        /// <summary>
        /// Recibe una cadena de Bits y devuelve el Mensaje Decodificado que representa.
        /// </summary>
        /// <param name="obj">Cadena de Bits que contiene el Mensaje Codificado</param>
        /// <param name="encodeMap">Mapa de Codificación con el que decodificará el Mensaje (generalmente nunca se especifica manualmente)</param>
        /// <returns>Mensaje Decodificado</returns>
        public static string FromBitArray(BitArray obj, EncodeMappings encodeMap = EncodeMappings.SmartMapping) {
            // > Hacer una validación rápida de tamaño
            if ( obj.Length < 6 )
                throw new Exception("El BitArray tiene muy pocos elementos para contener un mensaje codificado");
            
            // > Convertir BitArray en List<bool> para operar más fácilmente
            List<bool> objEncodedBits = new List<bool>();
            foreach ( bool bit in obj )
                objEncodedBits.Add(bit);

            // > Realizar decodificación y devolver resultado
            return Decode.FromBoolList(objEncodedBits, encodeMap);
        }

        public static string FromString(string obj, EncodeMappings encodeMap = EncodeMappings.SmartMapping) {
            // > Hacer una validación rápida de tamaño
            if ( obj.Length < 6 )
                throw new Exception("El Mensaje Portador tiene muy pocos carácteres como para contener un Mensaje Codificado");

            // > Inicializar Cadena de Bits que contiene el Mensaje Codificado
            List<bool> encodedBits = new List<bool>();

            // > Iterar en cada Carácter del Mensaje Portador y obtener los Bits
            foreach ( char chr in obj.ToCharArray() ) {
                // > Omitir éste carácter si no puede utilizarse para codificar
                if ( ! EncodeMaps.SupportedLetters.Contains(chr.ToString()) )
                    continue;

                // > Determinar si el Carácter es un Bit 1 ó 0
                if ( chr.ToString().ToUpper() == chr.ToString() )
                    encodedBits.Add(true);
                else
                    encodedBits.Add(false);
            }

            // > Devolver Mensaje Decodificado
            return Decode.FromBoolList(encodedBits, encodeMap);
        }
    }
}
